#include "ObjectManager.h"
#include "CollectQuest.h"
#include "BountyQuest.h"
#include "RaceQuest.h"
#include "CollisionManager.h"
#include "NormalEnemy.h"
#include "AlertEnemy.h"
#include "HeavyEnemy.h"
#include "FastEnemy.h"
#include "Patrol.h"
#include "Attack.h"



//instance pointer set to null
ObjectManager* ObjectManager::pInst = 0;

ObjectManager::ObjectManager()
{
	playerModel = 0;
	NPCModel = 0;
	HUD = 0;
	house = 0;
	OrbInstance = 0;
	numOrbsCollected = 0;
	numMedalsCollected = 0;
}

ObjectManager::~ObjectManager()
{

}

ObjectManager* ObjectManager::GetInstance()
{
	if(pInst == 0)
	{
		pInst = new ObjectManager();
	}

	return pInst;
}

void ObjectManager::Release()
{
	if(pInst)
	{
		delete pInst;
	}
}

void ObjectManager::Intialise(Terrain* terrain,VirtualCamera* camera,GraphicsInterface* graphics)
{
	//Store a pointer to the game terrain. We need this for the 
	//GetHeight() function. 
	pTerrain = terrain;

	/////////Player///////////////////////////

	//Alloc memory for player and init (load model)
	playerModel = new Player();
	playerModel->Init(graphics->GetDevice(), 
		XMFLOAT3(100.0f, terrain->GetHeight(100.0f, 50.0f), 50.0f), 
		XMFLOAT3(0.0f, 0.0f, 0.0f), 
		XMFLOAT3(0.001333f, 0.001333f, 0.001333f), 
		camera, terrain);

	////////////////NPC////////////////////////

	// Alloc memory for NPC
	NPCModel = new QuestGiver();

	// Load the model: position(0,0,0) for test purposes
	NPCModel->Intialise(graphics->GetDevice(),
		XMFLOAT3(20.0f, terrain->GetHeight(20.0f, 400.0f), 400.0f), 
		XMFLOAT3(MathHelper::ToRadians(-90.0), MathHelper::ToRadians(180.0f), 0.0f),
		XMFLOAT3(0.08f, 0.08f, 0.08f),
		terrain);


	///////////////Heavy enemies////////////////////
	std::vector<XMFLOAT3> temppatrol1;

	temppatrol1.push_back(XMFLOAT3(10.0f, terrain->GetHeight(1000.0f, 400.0f), 1100.0f));
	temppatrol1.push_back(XMFLOAT3(20.0f, terrain->GetHeight(1100.0f, 300.0f), 1000.0f));
	temppatrol1.push_back(XMFLOAT3(110.0f, terrain->GetHeight(1200.0f, 400.0f), 1100.0f));
	temppatrol1.push_back(XMFLOAT3(20.0f, terrain->GetHeight(1100.0f, 500.0f), 1200.0f));

	AbstractEnemy* temp = new HeavyEnemy();

	temp->SetPatrol(temppatrol1);

	temp->Intialise(graphics->GetDevice(),
		XMFLOAT3(1000.0f, terrain->GetHeight(1000.0f, 1000.0f), 1000.0f), 
		XMFLOAT3(0.0f, 0.0f, 0.0f),
		XMFLOAT3(0.04f, 0.04f, 0.04f),
		terrain,
		playerModel);

	EnemyList.push_back(temp);

	temp = new HeavyEnemy();

	temp->SetPatrol(temppatrol1);

	temp->Intialise(graphics->GetDevice(),
		XMFLOAT3(800.0f, terrain->GetHeight(800.0f, 800.0f), 800.0f), 
		XMFLOAT3(0.0f, 0.0f, 0.0f),
		XMFLOAT3(0.04f, 0.04f, 0.04f),
		terrain,
		playerModel);

	EnemyList.push_back(temp);

	temp = new HeavyEnemy();

	temp->SetPatrol(temppatrol1);

	temp->Intialise(graphics->GetDevice(),
		XMFLOAT3(900.0f, terrain->GetHeight(900.0f, 900.0f), 900.0f), 
		XMFLOAT3(0.0f, 0.0f, 0.0f),
		XMFLOAT3(0.04f, 0.04f, 0.04f),
		terrain,
		playerModel);

	EnemyList.push_back(temp);

	///////////////fast enemies////////////////////

	std::vector<XMFLOAT3> temppatrol2;
	temppatrol2.push_back(XMFLOAT3(200.0f, terrain->GetHeight(200.0f, 300.0f), 300.0f));
	temppatrol2.push_back(XMFLOAT3(300.0f, terrain->GetHeight(300.0f, 200.0f), 200.0f));
	temppatrol2.push_back(XMFLOAT3(400.0f, terrain->GetHeight(400.0f, 300.0f), 300.0f));
	temppatrol2.push_back(XMFLOAT3(300.0f, terrain->GetHeight(300.0f, 400.0f), 400.0f));

	temp = new FastEnemy();

	temp->SetPatrol(temppatrol2);

	temp->Intialise(graphics->GetDevice(),
		XMFLOAT3(300.0f, terrain->GetHeight(300.0f, 300.0f), 300.0f), 
		XMFLOAT3(0.0f, 0.0f, 0.0f),
		XMFLOAT3(0.004f, 0.004f, 0.004f),
		terrain,
		playerModel);

	EnemyList.push_back(temp);

	temp = new FastEnemy();

	temp->SetPatrol(temppatrol2);

	temp->Intialise(graphics->GetDevice(),
		XMFLOAT3(320.0f, terrain->GetHeight(320.0f, 280.0f), 280.0f), 
		XMFLOAT3(0.0f, 0.0f, 0.0f),
		XMFLOAT3(0.004f, 0.004f, 0.004f),
		terrain,
		playerModel);

	EnemyList.push_back(temp);

	temp = new FastEnemy();

	temp->SetPatrol(temppatrol2);

	temp->Intialise(graphics->GetDevice(),
		XMFLOAT3(280.0f, terrain->GetHeight(280.0f, 320.0f), 320.0f), 
		XMFLOAT3(0.0f, 0.0f, 0.0f),
		XMFLOAT3(0.004f, 0.004f, 0.004f),
		terrain,
		playerModel);

	EnemyList.push_back(temp);

	////////////////HUD/////////////////////////

	HUD = new GameHUD();
	HUD->Initalise(graphics->GetDevice());

	///////////////ORBS////////////////////////

	//Create orb
	OrbInstance = new InstancedOrbs(graphics->GetDevice(), OBJECT_MANAGER_INITIAL_NUM_ORBS);
	
	//Set orb locations
	std::vector<XMFLOAT3>orbLocations(OBJECT_MANAGER_INITIAL_NUM_ORBS);
	orbLocations[0] = XMFLOAT3(1481.0f, terrain->GetHeight(1481.0f, 310.0f), 310.0f);
	orbLocations[1] = XMFLOAT3(1520.0f, terrain->GetHeight(1520.0f, 318.0f), 318.0f);
	orbLocations[2] = XMFLOAT3(1469.0f, terrain->GetHeight(1469.0f, 212.0f), 212.0f);
	orbLocations[3] = XMFLOAT3(1613.0f, terrain->GetHeight(1613.0f, 217.0f), 217.0f);
	orbLocations[4] = XMFLOAT3(1546.0f, terrain->GetHeight(1546.0f, 374.0f), 374.0f);
	orbLocations[5] = XMFLOAT3(1536.0f, terrain->GetHeight(1536.0f, 381.0f), 381.0f);
	orbLocations[6] = XMFLOAT3(1565.0f, terrain->GetHeight(1565.0f, 262.0f), 262.0f);
	orbLocations[7] = XMFLOAT3(1455.0f, terrain->GetHeight(1455.0f, 378.0f), 378.0f);
	orbLocations[8] = XMFLOAT3(1519.0f, terrain->GetHeight(1519.0f, 351.0f), 351.0f);
	orbLocations[9] = XMFLOAT3(1617.0f, terrain->GetHeight(1617.0f, 247.0f), 247.0f);
	orbLocations[10] = XMFLOAT3(1513.0f, terrain->GetHeight(1513.0f, 259.0f), 259.0f);
	orbLocations[11] = XMFLOAT3(1635.0f, terrain->GetHeight(1635.0f, 362.0f), 362.0f);
	orbLocations[12] = XMFLOAT3(1563.0f, terrain->GetHeight(1563.0f, 378.0f), 378.0f);
	orbLocations[13] = XMFLOAT3(1480.0f, terrain->GetHeight(1480.0f, 312.0f), 312.0f);
	orbLocations[14] = XMFLOAT3(1460.0f, terrain->GetHeight(1460.0f, 381.0f), 381.0f);

	//Quest Orbs
	orbLocations[15] = XMFLOAT3(40.0f, terrain->GetHeight(40.0f, 400.0f), 400.0f);
	orbLocations[16] = XMFLOAT3(60.0f, terrain->GetHeight(60.0f, 400.0f), 400.0f);
	orbLocations[17] = XMFLOAT3(80.0f, terrain->GetHeight(80.0f, 400.0f), 400.0f);
	orbLocations[18] = XMFLOAT3(100.0f, terrain->GetHeight(100.0f, 400.0f), 400.0f);
	orbLocations[19] = XMFLOAT3(120.0f, terrain->GetHeight(120.0f, 400.0f), 400.0f);
	orbLocations[20] = XMFLOAT3(140.0f, terrain->GetHeight(140.0f, 400.0f), 400.0f);
	orbLocations[21] = XMFLOAT3(160.0f, terrain->GetHeight(160.0f, 400.0f), 400.0f);
	orbLocations[22] = XMFLOAT3(180.0f, terrain->GetHeight(180.0f, 400.0f), 400.0f);
	orbLocations[23] = XMFLOAT3(200.0f, terrain->GetHeight(200.0f, 400.0f), 400.0f);
	orbLocations[24] = XMFLOAT3(220.0f, terrain->GetHeight(220.0f, 400.0f), 400.0f);

	//Pass locations to the InstacedOrb class to init.
	OrbInstance->SetOrbLocations(orbLocations);

	////////////////////Quests/////////////////////////////

	////////////// collect quest/////////////////////
	std::vector<unsigned int> questlist;

	for(int i = 15; i < 25; i++)
	{
		questlist.push_back(i);
	}

	CollectQuest* tempcollect = new CollectQuest();
	tempcollect->SetCollectables(OrbInstance,questlist,20.0f);
	NPCModel->AddQuest(tempcollect);

	////////////////bounty quest///////////////////
	BountyQuest* tempbounty = new BountyQuest();

	temp = new HeavyEnemy();

	std::vector<XMFLOAT3> questpatrol;
	questpatrol.push_back(XMFLOAT3(80.0f, terrain->GetHeight(80.0f, 400.0f), 400.0f));

	temp->SetPatrol(questpatrol);

	temp->Intialise(graphics->GetDevice(),
		XMFLOAT3(80.0f, terrain->GetHeight(80.0f, 400.0f), 400.0f), 
		XMFLOAT3(0.0f, 0.0f, 0.0f),
		XMFLOAT3(0.004f, 0.004f, 0.004f),
		terrain,
		playerModel);

	EnemyList.push_back(temp);

	tempbounty->SetValues(temp);
	NPCModel->AddQuest(tempbounty);

	///////////////racequest////////////////////////
	RaceQuest* temprace = new RaceQuest;
	temprace->SetValues(XMFLOAT3(100.0f, terrain->GetHeight(100.0f, 50.0f), 50.0f),30.0f,10.0f);
	NPCModel->AddQuest(temprace);

}

void ObjectManager::Update(float delta)
{
	//update player
	playerModel->Update(delta);

	//update npc
	NPCModel->Update(delta,true,false);

	// Update the HUD (check for keyboard input)
	HUD->Update(delta);

	//update enemies
	for(std::vector<AbstractEnemy*>::iterator it = EnemyList.begin(); it < EnemyList.end(); it++)
	{
		if((*it))
		{
			(*it)->Update(delta);
		}
	}

	//check players current quest
	if(playerModel->GetCurrentQuest())
	{
		if(playerModel->GetCurrentQuest()->GetIsCompleted())
		{
			numMedalsCollected++;
			HUD->SetNumMedals(numMedalsCollected);
		}
	}
}

void ObjectManager::CheckCollisions()
{
	//get distance between player and npc
	float distance = CollisionManager::GetInstance()->CalculateUnsignedDistance(playerModel->GetPosition(),NPCModel->GetPosition());

	//checks collision with npx interaction radius
	if(NPCModel && playerModel)
	{
		if(distance < NPCModel->GetRadius())
		{
			if(!playerModel->GetCurrentQuest() && GetAsyncKeyState('E') & 0x8000)
			{
				// Gives the player a quest
				playerModel->SetCurrentQuest(NPCModel->GiveQuest());
			}
		}
	}

	// For collection of the bog standard orbs
	if(OrbInstance && playerModel)
	{
		for(int i = 0; i < OBJECT_MANAGER_INITIAL_NUM_ORBS; i++)
		{
			if(OrbInstance->GetIsActive(i))
			{
				// Test collision between player and orb collectable
				if(CollisionManager::GetInstance()->TestCollision(&playerModel->GetHitBox(),&OrbInstance->GetHitBox(i)))
				{
					OrbInstance->SetIsActive(i,false);

					if(i < 15)
					{
						// Increment each time a collision is detected
						numOrbsCollected ++;
						// Set the total number of orbs already collected in the HUD
						HUD->SetNumCollectables(numOrbsCollected);

						// All bog standard orbs have been collected so award the player with a medal
						if (numOrbsCollected == 15)
						{
							numMedalsCollected ++;
							HUD->SetNumMedals(numMedalsCollected);
						}
					}
				}
			}
		}
	}

	//run collsion checks with player agaisnt enemies
	if(playerModel)
	{
		for(std::vector<AbstractEnemy*>::iterator it = EnemyList.begin(); it < EnemyList.end(); it++)
		{
			if((*it))
			{
				if((*it)->GetIsAlive())
				{
					if(CollisionManager::GetInstance()->TestCollision(&(*it)->GetHitBox(),&playerModel->GetHitBox()))
					{
						//if the player can be dealt damage
						if(!playerModel->GetInvunerable() && !playerModel->GetAttacking())
						{
							//deal damage to player
							playerModel->SetHealth(playerModel->GetHealth()-(*it)->GetDamage());
							playerModel->SetInvunerable(true);
						}
						//if the enemy can be damaged
						else if(playerModel->GetAttacking() && !(*it)->GetInvunerable())
						{
							//damage enemy
							(*it)->SetHealth((*it)->GetHealth()-playerModel->GetDamage());
							(*it)->SetInvunerable(true);
						}
					}
				}
			}
		}
	}

	//if the player has died respawn
	if(playerModel->GetHealth() <= 0)
	{
		Respawn();
	}
}

void ObjectManager::AddAllToBatch(GraphicsInterface* graphics)
{
	//render player
	playerModel->AddToBatch(graphics);

	//render npc
	NPCModel->AddToBatch(graphics);

	//render orbs
	OrbInstance->AddToBatch(graphics);

	// Loop through each enemy and add to rendering batch
	for(std::vector<AbstractEnemy*>::iterator it = EnemyList.begin(); it < EnemyList.end(); it++)
	{
		if((*it))
		{
			//render enemy
			(*it)->AddToBatch(graphics);
		}
	}
}

void ObjectManager::HUDRender(GraphicsInterface* gi)
{
	if (HUD->GetIsActive())
	{
		// Draw the game HUD if it is toggled on
		// Begin 2D rendering (Text/HUD)
		// Render all HUD elements
		HUD->RenderHUD(gi, playerModel);
	}
}

void ObjectManager::ReleaseObjects()
{
	//delete player
	SafeDelete(playerModel);

	//delete npc
	SafeDelete(NPCModel);

	//delete orbs 
	SafeDelete(OrbInstance);

	//delete interface
	SafeDelete(HUD);

	//delete house
	SafeDelete(house);

	//delete enemies 
	while(!EnemyList.empty())
	{
		SafeDelete(EnemyList.front());
		EnemyList.pop_back();
	}

	//delete state instances
	Attack::GetInstance()->Release();
	Patrol::GetInstance()->Release();

}

void ObjectManager::Respawn()
{
	//set positon to spawnpoint and reset health
	playerModel->SetHealth(6);
	playerModel->Respawn();

	//reset enemy A.I
	for(std::vector<AbstractEnemy*>::iterator it = EnemyList.begin(); it < EnemyList.end(); it++)
	{
		if((*it))
		{
			(*it)->ChangeState(Patrol::GetInstance());
		}
	}
}